/*
 * FakeLabel.java
 *
 * Created on November 16, 2006, 1:38 AM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package edu.umn.cs5115.scheduler.framework.gui;

import edu.umn.cs5115.scheduler.framework.Application;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;

/**
 * A fake widget that displays text
 * @author grant
 */
public class Label extends FakeWidget
{
    /** Key for the label's text. */
    public final static String TEXT_KEY = "text";
    
    /** the default shadow color for new labels. */
    private final static AlphaColor DEFAULT_SHADOW_COLOR = new AlphaColor(255, 255, 255, 64);
    
    /** A GC to use for measurements. */
    private GC g;
    
    /** Font to use to display the text. */
    private Font font = new Font();
    /** Color to draw the text in. */
    private AlphaColor textColor = AlphaColor.BLACK;
    /** Color to draw the text's shadow in. */
    private AlphaColor shadowColor = DEFAULT_SHADOW_COLOR;
    /** Offset of shadow relative to text. */
    private Point shadowOffset = new Point(1, 1);
    /** String value of the text. */
    private String text = "";
    /** A wrapped version of the text. */
    private String wrappedText = "";
    /** True if this label mirrors it's parent's tool tip. */
    private boolean mirrorParentToolTip = true;
    /** Binder that binds our tool tip to our parents if we're mirroring. */
    private Binder toolTipBinder;
    
    /** Creates a new instance of FakeLabel */
    public Label()
    {
    }
    
    protected void setParent(FakeWidget parent)
    {
        super.setParent(parent);
        if (mirrorParentToolTip)
            toolTipBinder = new Binder(parent, FakeWidget.TOOL_TIP_KEY, this, FakeWidget.TOOL_TIP_KEY);
    }
    
    public void removeFromParent()
    {
        if (toolTipBinder != null)
            toolTipBinder.unbind();
        if (g != null)
        {
            g.dispose();
            g = null;
        }
    }
    
    public void setText(String text)
    {
        this.text = text;
        didChangeValueForKey(TEXT_KEY);
    }
    
    public String getText()
    {   
        return text;
    }
    
    /**
     * Sets the label's width.  The height will be automatically calculated 
     * based on the text of the label.
     * @param width The width to size to.
     * @return How much of the width is actually used by the labels text.  The
     * label could be safely sized to this value without changing the wrapping
     * of the label.
     */
    public int resizeToWidth(int width)
    {
        WrapDetails details = wrapToBox(new WrapDetails(text, width-shadowOffset.x, -1));
        wrappedText = details.text;
        setSize(width, details.textHeight+shadowOffset.y);
        return details.textWidth+shadowOffset.x;
    }
    
    /**
     * Produces a wrapped and truncated version of the string.  The string
     * is wrapped using the Graphic context's current font.
     * @param details Holds the parameters.  Set height to -1 if you don't care.
     * @return The same WrapDetails object passed as a parameter, but with the
     * values modified:  The text field has been wrapped, the textHeight has 
     * been set to space consumed.
     */
    private WrapDetails wrapToBox(WrapDetails details)
    {
        // TODO: Optimize this function.  The majority of the lag in resizing
        // is due to this function.
        // Could also try to reduce calls to it, but optimizing here would 
        // definitely help.
        String text = details.text;
        int textHeight = details.textHeight;
        int textWidth = details.textWidth;
        int usedWidth = 0;
        if (g == null)
            g = new GC(getDisplay());
        if (text == null || text.length() < 1)
            return details;
        boolean allFits;
        StringBuffer output = new StringBuffer();
        StringBuffer thisLine = new StringBuffer();

        String remainingText = text;

        font.setFont(g);
        int lineHeight = g.getFontMetrics().getHeight();

        int lines = 1;
        while((textHeight < 0 || lines * lineHeight < textHeight) && remainingText.length() > 0)
        {
            int blankCharacters = 0;
            boolean wroteLine = false;
            while(blankCharacters < remainingText.length() && remainingText.charAt(blankCharacters) == ' ')
                blankCharacters++;
            remainingText = remainingText.substring(blankCharacters);
            int i;
            for(i = 0; i < remainingText.length(); i++)
            {
                thisLine.append(remainingText.charAt(i));
                int thisWidth = g.stringExtent(thisLine.toString()).x;
                if (thisWidth>textWidth)
                {
                    int usedCharacters = thisLine.lastIndexOf(" ");
                    if (usedCharacters <= 0)
                    {
                        usedCharacters = i;
                        // ensure the string always gets shorter
                        if (usedCharacters < 1)
                            usedCharacters = 1;
                    }
                    if (lines > 1)
                        output.append("\n");
                    output.append(thisLine.substring(0, usedCharacters));
                    thisLine.delete(0, thisLine.length());
                    lines++;
                    remainingText = remainingText.substring(usedCharacters);
                    wroteLine = true;
                    break;
                } else {
                    if (thisWidth > usedWidth)
                        usedWidth = thisWidth;
                }
            }
            if (!wroteLine)
            {
                if (lines > 1)
                    output.append("\n");
                output.append(thisLine);
                remainingText = "";
            }
        }

        details.text = output.toString();
        details.textHeight = lineHeight * lines;
        details.textWidth = usedWidth;
        return details;
    }
    
    /** Holds results of wrapToBox method */
    private static class WrapDetails
    {
        public String text;
        public int textWidth = -1;
        public int textHeight = -1;
        
        public WrapDetails() {}
        public WrapDetails(String text, int textWidth, int textHeight)
        {
            this.text = text;
            this.textWidth = textWidth;
            this.textHeight = textHeight;
        }
    }

    public void paintComponent(GC g)
    {
        font.setFont(g);
        shadowColor.setForeground(g);
        g.drawText(wrappedText, shadowOffset.x, shadowOffset.y, true);
        textColor.setForeground(g);
        g.drawText(wrappedText, 0, 0, true);
    }

    public Font getFont()
    {
        return font;
    }

    public void setFont(Font font)
    {
        this.font = font;
    }

    public AlphaColor getTextColor()
    {
        return textColor;
    }

    public void setTextColor(AlphaColor textColor)
    {
        this.textColor = textColor;
    }

    public AlphaColor getShadowColor()
    {
        return shadowColor;
    }

    public void setShadowColor(AlphaColor shadowColor)
    {
        this.shadowColor = shadowColor;
    }

    public Point getShadowOffset()
    {
        return shadowOffset;
    }

    public void setShadowOffset(Point shadowOffset)
    {
        this.shadowOffset = shadowOffset;
    }
    
    /** Let mouse events fall through labels. */
    public boolean mouseDown(MouseEvent e) { return false; }
    /** Let mouse events fall through labels. */
    public boolean mouseUp(MouseEvent e) { return false; }
    /** Let mouse events fall through labels. */
    public boolean mouseDoubleClick(MouseEvent e) { return false; }
}
